﻿using Devonline.Entity;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;

namespace Devonline.Communication.Server;

/// <summary>
/// 消息集线器
/// string 作为主键类型
/// </summary>
public class MessageServer : CommunicationServer<IMessageCommunicatorClient>
{
    private const string LOG_INFO = "Server receive the {messageType} message from user {user} in client {client} to {receiver} with content: ";
    public MessageServer(
        ILogger<MessageServer> logger,
        IDistributedCache cache,
        IHttpContextAccessor httpContextAccessor,
        HostSetting hostSetting
        ) : base(logger, cache, httpContextAccessor, hostSetting) { }

    #region 供客户端调用的公开方法
    /// <summary>
    /// 服务器转发消息
    /// </summary>
    /// <param name="message"></param>
    /// <returns></returns>
    public virtual async Task Send(Message message)
    {
        try
        {
            //填充消息编号和消息当前连接
            if (string.IsNullOrWhiteSpace(message.Id))
            {
                message.Id = KeyGenerator.GetKey().ToString();
            }

            message.Sender = User;
            message.SendTime = DateTime.UtcNow;
            message.From = Client;

            var key = _hostSetting.CachePrefix + message.Id;
            await _cache.SetStringAsync(key, message.ToJsonString());

            if (string.IsNullOrWhiteSpace(message.Receiver) && string.IsNullOrWhiteSpace(message.Group))
            {
                //发给所有人
                _logger.LogInformation(LOG_INFO + message.Content, message.Type, User, Client, "All");
                await Clients.AllExcept(new string[] { Context.ConnectionId }).Receive(message);
            }
            else if (string.IsNullOrWhiteSpace(message.Receiver) && (!string.IsNullOrWhiteSpace(message.Group)))
            {
                //发给组中的每个人
                _logger.LogInformation(LOG_INFO + message.Content, message.Type, User, Client, message.Group);
                //TODO, 组需要数据支持, 此处暂不实现
                //await base.SendAsync(message, message.Receiver);
            }
            else
            {
                _logger.LogInformation(LOG_INFO + message.Content, message.Type, User, Client, message.Receiver);
                await base.SendAsync(message, message.Receiver);
            }

            //记录日志
            LogMessage(message);
        }
        catch (Exception ex)
        {
            LogException(ex, message);
        }
    }
    /// <summary>
    /// 服务器批量转发消息的方法
    /// </summary>
    /// <param name="messages"></param>
    /// <returns></returns>
    public virtual void Sends(IEnumerable<Message> messages) => Parallel.ForEach(messages, async message => await Send(message));
    /// <summary>
    /// 服务器接收消息回执方法
    /// 设置原始消息的接收者和接收时间
    /// </summary>
    /// <param name="message">回执的消息</param>
    /// <returns></returns>
    public virtual async Task Ack(Message message)
    {
        try
        {
            var key = _hostSetting.CachePrefix + message.Id;
            var originalMessage = await GetFromCacheAsync<string>(key);
            originalMessage.To = message.From;
            originalMessage.ReceiveTime = DateTime.UtcNow;
            await _cache.SetStringAsync(key, originalMessage.ToJsonString());

            //转发回执
            ArgumentNullException.ThrowIfNull(originalMessage.Sender);
            message.Type = MessageType.Ack;
            message.Content = originalMessage.Type.ToString();
            message.ReceiveTime = DateTime.UtcNow;
            await Clients.Group(originalMessage.Sender).Ack(message);

            //记录日志
            LogMessage(message);
        }
        catch (Exception ex)
        {
            LogException(ex, nameof(Ack));
        }
    }
    /// <summary>
    /// 服务器接收消息已读方法
    /// 设置原始消息的已读时间
    /// </summary>
    /// <param name="message">已读的消息</param>
    /// <returns></returns>
    public virtual async Task Read(Message message)
    {
        try
        {
            var key = _hostSetting.CachePrefix + message.Id;
            var originalMessage = await GetFromCacheAsync<string>(key);
            originalMessage.ReadTime = DateTime.UtcNow;
            await _cache.SetStringAsync(key, originalMessage.ToJsonString());

            //转发已读
            ArgumentNullException.ThrowIfNull(originalMessage.Sender);
            message.Type = MessageType.Read;
            message.Content = originalMessage.Type.ToString();
            message.ReadTime = DateTime.UtcNow;
            await Clients.Group(originalMessage.Sender).Read(message);

            //记录日志
            LogMessage(message);
        }
        catch (Exception ex)
        {
            LogException(ex, nameof(Read));
        }
    }
    /// <summary>
    /// 强制客户端通讯器退出
    /// 创建并保存一个 Command 类型的新消息, 记录退出相关时间
    /// 退出命令只有离线方法中查询数据库才能找到并设置离线方法, 在设置其接收/已读时间
    /// TODO 此命令功能有待加强, 客户端有自动断线重连的能力, 不会主动退出, 因此如果要求客户端退出, 需要客户端配置
    /// </summary>
    /// <param name="message">退出的消息</param>
    /// <returns></returns>
    public virtual async Task Abort(Message message)
    {
        try
        {
            ArgumentNullException.ThrowIfNull(message.Receiver);

            //填充消息编号和消息当前连接
            message.Id = KeyGenerator.GetKey().ToString();
            message.Type = MessageType.Abort;
            message.Sender = User;
            message.SendTime = DateTime.UtcNow;
            message.From = Client;
            message.Content = nameof(Abort);

            var key = _hostSetting.CachePrefix + message.Id;
            await _cache.SetStringAsync(key, message.ToJsonString());

            //退出指定客户端
            await Clients.Group(message.Receiver).Abort();

            //记录日志
            LogMessage(message);
        }
        catch (Exception ex)
        {
            LogException(ex, nameof(Abort));
        }
    }
    /// <summary>
    /// 服务器接收心跳消息, 心跳消息为客户端保活信号
    /// </summary>
    /// <param name="message"></param>
    /// <returns></returns>
    public virtual async Task Heartbeat()
    {
        try
        {
            var message = new Message
            {
                Id = Client ?? string.Empty,
                Type = MessageType.Command,
                Sender = User,
                From = Client,
                CreateTime = DateTime.UtcNow,
                Content = nameof(Heartbeat)
            };

            LogMessage(message);
            var messageKey = _hostSetting.CachePrefix + nameof(Heartbeat) + AppSettings.CHAR_UNDERLINE + message.Sender + AppSettings.CHAR_UNDERLINE + message.From;
            await _cache.SetStringAsync(messageKey, message.ToJsonString());
        }
        catch (Exception ex)
        {
            LogException(ex, nameof(Heartbeat));
        }
    }
    #endregion

    #region 内部成员
    /// <summary>
    /// 转发消息已读的方法
    /// </summary>
    /// <param name="message"></param>
    /// <returns></returns>
    protected virtual async Task ReadAsync(Message message)
    {
        try
        {
            //验证消息接收者
            if (string.IsNullOrWhiteSpace(message.Receiver))
            {
                throw new ArgumentNullException(nameof(message), "The message has no receiver");
            }

            //填充消息编号和消息当前连接
            message.Id ??= KeyGenerator.GetKey().ToString();
            message.Sender ??= User;
            message.SendTime ??= DateTime.UtcNow;
            message.From ??= Client;

            var key = _hostSetting.CachePrefix + message.Id;
            await _cache.SetStringAsync(key, message.ToJsonString());

            if (string.IsNullOrWhiteSpace(message.Receiver))
            {
                _logger.LogError("Receiver must not be null");
                return;
            }

            //转发消息
            await Clients.Group(message.Receiver).Read(message);

            //记录日志
            LogMessage(message);
        }
        catch (Exception ex)
        {
            LogException(ex, message);
        }
    }
    /// <summary>
    /// 记录消息日志
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="message"></param>
    protected virtual void LogMessage<T>(Message<T> message)
    {
        if (message.Content == null && string.IsNullOrWhiteSpace(message.Receiver))
        {
            _logger.LogInformation("Server receive the {messageType} from user {user} in client {client} with message id is: " + message.Id, message.Type, message.Sender, message.From);
        }
        else
        {
            var receiver = message.Group ?? string.Empty;
            receiver += (receiver.IsNullOrWhiteSpace() ? string.Empty : "@") + (message.Receiver ?? Receiver);
            _logger.LogInformation(LOG_INFO + message.Content, message.Type, message.Sender, message.From, receiver);
        }
    }
    /// <summary>
    /// 记录异常
    /// </summary>
    /// <param name="ex"></param>
    /// <param name="method"></param>
    protected virtual void LogException(Exception ex, string method) => _logger.LogError(ex, "Server receive the {messageType} message from user {user} in client {client} throw exception", method, User, Client);
    /// <summary>
    /// 记录异常
    /// </summary>
    /// <param name="ex"></param>
    /// <param name="message"></param>
    protected virtual void LogException<T>(Exception ex, Message<T> message) => _logger.LogError(ex, LOG_INFO + message.Content + " throw exception", message.Type, message.Sender, message.From, message.Receiver);
    /// <summary>
    /// 从缓存中获取消息
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="message"></param>
    /// <returns></returns>
    /// <exception cref="Exception"></exception>
    protected virtual async Task<Message<T>> GetFromCacheAsync<T>(string key)
    {
        var cache = await _cache.GetStringAsync(key);
        if (string.IsNullOrWhiteSpace(cache))
        {
            throw new Exception($"The message of id {key} not found!");
        }

        var message = cache.ToJsonObject<Message<T>>();
        if (message == null)
        {
            throw new Exception($"The message of id {key} not found!");
        }

        return message;
    }
    #endregion
}